#include "const/oPreDefine.h"
#include "model/oActionInterval.h"
#include "model/oAnimationCache.h"

//oAnimateTo
oAnimateTo::oAnimateTo():
m_flag(oAnimateTo::None),
m_easeFlag(oAnimateTo::None),
m_easePos(nullptr),
m_easeScale(nullptr),
m_easeRotate(nullptr),
m_easeOpacity(nullptr)
{ }

void oAnimateTo::setEase(uint32 type, oEaseFunc func)
{
	if (func)
	{
		m_easeFlag |= type;
	}
	else
	{
		m_easeFlag &= ~type;
	}
	switch (type)
	{
	case oAnimateTo::Pos:
		m_easePos = func;
		break;
	case oAnimateTo::Scale:
		m_easeScale = func;
		break;
	case oAnimateTo::Rotate:
		m_easeRotate = func;
		break;
	case oAnimateTo::Opacity:
		m_easeOpacity = func;
		break;
	}
}

bool oAnimateTo::initWithDuration( ccTime duration, float posX, float posY, float scaleX, float scaleY, float rotate, GLubyte opacity )
{
	if (CCActionInterval::initWithDuration(duration))
	{
		m_fEndScaleX = scaleX;
		m_fEndScaleY = scaleY;
		m_fEndPosX = posX;
		m_fEndPosY = posY;
		m_fEndRotate = rotate;
		m_nEndOpacity = opacity;
		return true;
	}
	return false;
}

CCObject* oAnimateTo::copyWithZone( CCZone* pZone )
{
	return nullptr;
}

void oAnimateTo::startWithTarget( CCNode *pTarget )
{
	CCAssert(dynamic_cast<CCSprite*>(pTarget) != nullptr, "For CCSprite Only!");
	CCActionInterval::startWithTarget(pTarget);

	m_flag = oAnimateTo::None;

	m_fStartPosX = pTarget->getPositionX();
	m_fStartPosY = pTarget->getPositionY();
	if (m_fStartPosX != m_fEndPosX || m_fStartPosY != m_fEndPosY)
	{
		m_flag |= oAnimateTo::Pos;
		m_fDeltaPosX = m_fEndPosX - m_fStartPosX;
		m_fDeltaPosY = m_fEndPosY - m_fStartPosY;
	}

	m_fStartRotate = pTarget->getRotation();
	if (m_fStartRotate != m_fEndRotate)
	{
		m_flag |= oAnimateTo::Rotate;
		m_fDeltaRotate = m_fEndRotate - m_fStartRotate;
	}

	m_fStartScaleX = pTarget->getScaleX();
	m_fStartScaleY = pTarget->getScaleY();
	if (m_fStartScaleX != m_fEndScaleX || m_fStartScaleY != m_fEndScaleY)
	{
		m_flag |= oAnimateTo::Scale;
		m_fDeltaScaleX = m_fEndScaleX - m_fStartScaleX;
		m_fDeltaScaleY = m_fEndScaleY - m_fStartScaleY;
	}

	m_nStartOpacity = ((CCSprite*)m_pTarget)->getOpacity();
	if (m_nStartOpacity != m_nEndOpacity)
	{
		m_flag |= oAnimateTo::Opacity;
		m_nDeltaOpacity = (short)m_nEndOpacity - (short)m_nStartOpacity;
	}
}

void oAnimateTo::update( ccTime time )
{
	if (m_easeFlag)
	{
		if (m_flag & oAnimateTo::Opacity)
		{
			if (m_easeOpacity)
				((CCSprite*)m_pTarget)->setOpacity(GLubyte(m_easeOpacity(time, m_nStartOpacity, m_nDeltaOpacity)));
			else ((CCSprite*)m_pTarget)->setOpacity(GLubyte(m_nStartOpacity + m_nDeltaOpacity * time));
		}
		if (m_flag & oAnimateTo::Pos)
		{
			if (m_easePos)
				m_pTarget->setPosition(
					m_easePos(time, m_fStartPosX, m_fDeltaPosX),
					m_easePos(time, m_fStartPosY, m_fDeltaPosY));
			else m_pTarget->setPosition(m_fStartPosX + m_fDeltaPosX * time, m_fStartPosY + m_fDeltaPosY * time);
		}
		if (m_flag & oAnimateTo::Rotate)
		{
			if (m_easeRotate)
				m_pTarget->setRotation(m_easeRotate(time, m_fStartRotate, m_fDeltaRotate));
			else m_pTarget->setRotation(m_fStartRotate + m_fDeltaRotate * time);
		}
		if (m_flag & oAnimateTo::Scale)
		{
			if (m_easeScale)
			{
				m_pTarget->setScaleX(m_easeScale(time, m_fStartScaleX, m_fDeltaScaleX));
				m_pTarget->setScaleY(m_easeScale(time, m_fStartScaleY, m_fDeltaScaleY));
			}
			else
			{
				m_pTarget->setScaleX(m_fStartScaleX + m_fDeltaScaleX * time);
				m_pTarget->setScaleY(m_fStartScaleY + m_fDeltaScaleY * time);
			}
		}
	}
	else
	{
		if (m_flag & oAnimateTo::Opacity)
		{
			((CCSprite*)m_pTarget)->setOpacity(GLubyte(m_nStartOpacity + m_nDeltaOpacity * time));
		}
		if (m_flag & oAnimateTo::Pos)
		{
			m_pTarget->setPosition(m_fStartPosX + m_fDeltaPosX * time, m_fStartPosY + m_fDeltaPosY * time);
		}
		if (m_flag & oAnimateTo::Rotate)
		{
			m_pTarget->setRotation(m_fStartRotate + m_fDeltaRotate * time);
		}
		if (m_flag & oAnimateTo::Scale)
		{
			m_pTarget->setScaleX(m_fStartScaleX + m_fDeltaScaleX * time);
			m_pTarget->setScaleY(m_fStartScaleY + m_fDeltaScaleY * time);
		}
	}
}

oAnimateTo* oAnimateTo::actionWithDuration( ccTime duration, float posX, float posY, float scaleX, float scaleY, float rotate, GLubyte opacity )
{
	oAnimateTo* pAnimateTo = new oAnimateTo();
	pAnimateTo->initWithDuration(duration, posX, posY, scaleX, scaleY, rotate, opacity);
	pAnimateTo->autorelease();
	return pAnimateTo;
}

//
// IntervalAction
//
oActionInterval::oActionInterval():
m_pause(false),
m_speed(1.0f)
{ }

oActionInterval* oActionInterval::actionWithDuration(ccTime d)
{
	oActionInterval *pAction = new oActionInterval();
	pAction->initWithDuration(d);
	pAction->autorelease();

	return pAction;
}

bool oActionInterval::initWithDuration(ccTime d)
{
	m_fDuration = d;

	// prevent division by 0
	// This comparison could be in step:, but it might decrease the performance
	// by 3% in heavy based action games.
	if (m_fDuration == 0)
	{
		m_fDuration = FLT_EPSILON;
	}

	m_elapsed = 0;

	return true;
}

CCObject* oActionInterval::copyWithZone(CCZone *pZone)
{
	return nullptr;
}

bool oActionInterval::isDone()
{
	return m_elapsed >= m_fDuration;
}

void oActionInterval::step(ccTime dt)
{
	if (m_pause)
	{
		return;
	}
	m_elapsed += (dt * m_speed);
	ccTime time = m_elapsed/m_fDuration;
	update(1.0f > time ? time : 1.0f);
}

void oActionInterval::setAmplitudeRate(CGFloat amp)
{
    CC_UNUSED_PARAM(amp);
	// Abstract class needs implementation
	CCAssert(0, "");
}

CGFloat oActionInterval::getAmplitudeRate()
{
	// Abstract class needs implementation
	CCAssert(0, "");
	return 0;
}

void oActionInterval::startWithTarget(CCNode *pTarget)
{
	CCFiniteTimeAction::startWithTarget(pTarget);
	m_elapsed = 0.0f;
	m_pause = false;
}

oActionInterval* oActionInterval::reverse()
{
	/*
	 NSException* myException = [NSException
								exceptionWithName:@"ReverseActionNotImplemented"
								reason:@"Reverse Action not implemented"
								userInfo:nil];
	@throw myException;	
	*/
	return nullptr;
}

void oActionInterval::setSpeed( float speed )
{
	m_speed = speed;
}

float oActionInterval::getSpeed() const
{
	return m_speed;
}

void oActionInterval::setTime(ccTime var)
{
	if (var > 1.0f)
	{
		var = 1.0f;
	}
	else if (var < 0)
	{
		var = 0;
	}
	m_elapsed = m_fDuration * var;
	update(var);
}

ccTime oActionInterval::getTime() const
{
	return m_elapsed / m_fDuration;
}

void oActionInterval::pause()
{
	m_pause = true;
}

bool oActionInterval::isPaused() const
{
	return m_pause;
}

void oActionInterval::resume()
{
	m_pause = false;
}

//
// Sequence
//
oSequence* oSequence::actionOneTwo(CCFiniteTimeAction *pActionOne, CCFiniteTimeAction *pActionTwo)
{
	oSequence *pSequence = new oSequence();
	pSequence->initOneTwo(pActionOne, pActionTwo);
	pSequence->autorelease();

	return pSequence;
}

oSequence* oSequence::actions(CCFiniteTimeAction *pAction1, ...)
{
	va_list params;
	va_start(params, pAction1);

	CCFiniteTimeAction *pNow;
	CCFiniteTimeAction *pPrev = pAction1;

	while (pAction1)
	{
		pNow = va_arg(params, CCFiniteTimeAction*);
		if (pNow)
		{
			pPrev = actionOneTwo(pPrev, pNow);
		}
		else
		{
			break;
		}
	}

	va_end(params);
	return (oSequence*)pPrev;
}

oSequence* oSequence::actionsWithArray(CCArray *actions)
{
	CCFiniteTimeAction* prev = (CCFiniteTimeAction*)actions->objectAtIndex(0);
	for (unsigned int i = 1; i < actions->count(); ++i)
	{
		prev = actionOneTwo(prev, (CCFiniteTimeAction*)actions->objectAtIndex(i));
	}
	return (oSequence*)prev;
}

bool oSequence::initOneTwo(CCFiniteTimeAction *pActionOne, CCFiniteTimeAction *pActionTwo)
{
	CCAssert(pActionOne != nullptr, "");
	CCAssert(pActionTwo != nullptr, "");

	ccTime d = pActionOne->getDuration() + pActionTwo->getDuration();
	oActionInterval::initWithDuration(d);

	m_pActions[0] = pActionOne;
	pActionOne->retain();

	m_pActions[1] = pActionTwo;
	pActionTwo->retain();

	return true;
}

CCObject* oSequence::copyWithZone(CCZone *pZone)
{
	return nullptr;
}

oSequence::~oSequence()
{
	CC_SAFE_RELEASE(m_pActions[0]);
	CC_SAFE_RELEASE(m_pActions[1]);
}

void oSequence::startWithTarget(CCNode *pTarget)
{
	oActionInterval::startWithTarget(pTarget);
	m_split = m_pActions[0]->getDuration() / m_fDuration;
	m_last = -1;
}

void oSequence::stop()
{
	m_pActions[0]->stop();
	m_pActions[1]->stop();
	oActionInterval::stop();
}

void oSequence::update(ccTime time)
{
	int found = 0;
	ccTime new_t = 0.0f;

	if (time >= m_split)
	{
		found = 1;
		if (m_split == 1)
		{
			new_t = 1;
		}
		else
		{
			new_t = (time - m_split) / (1 - m_split);
		}
	}
	else
	{
		found = 0;
		if (m_split != 0)
		{
			new_t = time / m_split;
		}
		else
		{
			new_t = 1;
		}
	}

	if (m_last == -1 && found == 1)
	{
		m_pActions[0]->startWithTarget(m_pTarget);
		m_pActions[0]->update(1.0f);
		m_pActions[0]->stop();
	}

	if (m_last != found)
	{
		if (m_last != -1)
		{
			m_pActions[m_last]->update(1.0f);
			m_pActions[m_last]->stop();
		}

		m_pActions[found]->startWithTarget(m_pTarget);
	}

	m_pActions[found]->update(new_t);
	m_last = found;
}

oActionInterval* oSequence::reverse()
{
	return oSequence::actionOneTwo(m_pActions[1]->reverse(), m_pActions[0]->reverse());
}

CCFiniteTimeAction* oSequence::one()
{
	return m_pActions[0];
}

CCFiniteTimeAction* oSequence::two()
{
	return m_pActions[1];
}

void oFrame::update( ccTime time )
{
	int current = (int)(time/_interval);
	if (current != _lastFrameIndex && current < (int)_def->rects.size())
	{
		_lastFrameIndex = current;
		((CCSprite*)m_pTarget)->setTextureRect(_def->rects[current]);
	}
	if (time == 1.0f && _loop)
	{
		oFrame::initWithDuration(oFrame::getDuration());
	}
}

void oFrame::startWithTarget( CCNode *pTarget )
{
	oActionInterval::startWithTarget(pTarget);
	_lastFrameIndex = 0;
	((CCSprite*)m_pTarget)->setTextureRect(_def->rects[0]);
}

oActionInterval* oFrame::reverse()
{
	return nullptr;
}

CCObject* oFrame::copyWithZone( CCZone* pZone )
{
	return nullptr;
}

oFrame* oFrame::actionWithDef( oFrameActionDef* def )
{
	oFrame* frame = new oFrame();
	frame->_def = def;
	frame->_loop = false;
	frame->_def->retain();
	frame->_interval = 1.0f / def->rects.size();//in update() total time is 1.0f
	frame->initWithDuration(def->duration);
	frame->autorelease();
	return frame;
}

oFrame::~oFrame()
{
	_def->release();
	_def = nullptr;
}

void oFrame::setLoop( bool loop )
{
	_loop = loop;
}